﻿using UnityEngine;
using System;
using BestHTTP;
using GF.Debug;

namespace Semisugar.Network
{
    [ExecuteInEditMode]
    public class WWWTaskRoutine : MonoBehaviour
    {
        public delegate void OnDataRetrievedCallback(WWWTaskRoutine task, byte[] data);
        public delegate void OnDataErrorCallback(WWWTaskRoutine task, string error);

        private string _url;
        private OnDataRetrievedCallback _callback = null;
        private OnDataErrorCallback _errorCallback = null;
        private bool _destroyWhenFinish = true;
        private int _timeOutMs = 5000;
        private byte[] _postData = null;
        private HTTPRequest _req = null;

        public string _lastError = null;
        public string LastErr
        {
            get { return _lastError; }
        }

        private bool _isWorking = false;
        // expose to user
        public string url
        {
            get { return _url; }
        }

        public object userdata = null;
        private static byte[] EMPTY_BUFFER = new byte[0]; // to avoid null-reference issue

        public static WWWTaskRoutine StartTask(string url, OnDataRetrievedCallback cb, OnDataErrorCallback errCb = null, bool destroyWhenFinish = true, int timeOutMs = 3000)
        {
            return StartTask(url, null, cb, errCb, destroyWhenFinish, timeOutMs);
        }

        public static WWWTaskRoutine StartTask(string url, byte[] postData, OnDataRetrievedCallback cb, OnDataErrorCallback errCb = null, bool destroyWhenFinish = true, int timeOutMs = 3000)
        {
            if (string.IsNullOrEmpty(url) == false)
            {
                GameObject go = new GameObject("_www_task");
                go.hideFlags = HideFlags.HideAndDontSave;
                WWWTaskRoutine task = go.AddComponent<WWWTaskRoutine>();
                task._callback = cb;
                task._errorCallback = errCb;
                task._url = url;
                task._destroyWhenFinish = destroyWhenFinish;
                task._timeOutMs = timeOutMs;
                task._postData = postData;
                task.StartTask();
                return task;
            }
            return null;
        }

        private void StartTask()
        {
            if (!_isWorking)
            {
                HTTPMethods callMethod = _postData != null && _postData.Length > 0 ? HTTPMethods.Post : HTTPMethods.Get;
                HTTPRequest req = new HTTPRequest(new Uri(url + "?p=" + DateTime.UtcNow.Ticks), callMethod, OnHttpRequestFinished);
                if (callMethod == HTTPMethods.Post)
                    req.AddBinaryData("data", _postData);

                req.Timeout = TimeSpan.FromMilliseconds(_timeOutMs);
                req.ConnectTimeout = TimeSpan.FromMilliseconds(_timeOutMs);
                req.DisableCache = true;
                req.Send();
                _req = req;
                _isWorking = true;
            }
        }

        void OnHttpRequestFinished(HTTPRequest request, HTTPResponse response)
        {
            bool isFinished = false;
            if (request.State == HTTPRequestStates.Finished)
            {
                if (response.IsSuccess)
                {
                    if (_callback != null)
                        _callback(this, response.Data);
                }
                else
                {
                    if (_errorCallback != null)
                        _errorCallback(this, string.Format("{0}: {1}\r\n{2}", response.StatusCode, response.Message, response.DataAsText));
                }
                isFinished = true;
            }
            else if (request.State == HTTPRequestStates.ConnectionTimedOut
                || request.State == HTTPRequestStates.TimedOut)
            {
                Debugger.LogError(string.Format("WWWTaskRoutine: Request '{0}' Time Out", _url));
                isFinished = true;
                if (_errorCallback != null)
                    _errorCallback(this, "Request time out.");
            }
            else if (request.State == HTTPRequestStates.Error)
            {
                if (request.Exception != null)
                {
                    if (_errorCallback != null)
                        _errorCallback(this, request.Exception.Message);
                }
                else
                {
                    if (_errorCallback != null)
                        _errorCallback(this, "Unknown Failure.");
                }
                isFinished = true;
            }
            else if (request.State == HTTPRequestStates.Aborted)
            {
                Debug.Log(string.Format("WWWTaskRoutine: the request '{0}' has been aborted.", _url));
                isFinished = true;
            }

            if (isFinished)
            {
                _isWorking = false;
                if (_destroyWhenFinish)
                {
                    if (Application.isPlaying)
                    {
                        GameObject.Destroy(gameObject);
                    }
                    else
                    {
                        GameObject.DestroyImmediate(gameObject);
                    }
                }
            }
        }

        public void Stop()
        {
            if (_isWorking)
            {
                _req.Abort();
            }
        }
    }
}